package com.zeng.ssm.service.support.idempotent;

import cn.hutool.core.lang.UUID;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.google.common.collect.Sets;
import com.zeng.ssm.api.support.idempotent.DistributedLockService;
import com.zeng.ssm.api.support.idempotent.ParamIdempotent;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;

import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;

/**
 * 根据请求参数实现幂等性的切面
 * <p>
 * 利用Redis分布式锁/Memcached
 * <p>
 * 该aop包裹事务aop
 *
 * @author zengwanlin
 * @date 2021-11-01T11:21
 */
@Component
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
public class ParamIdempotentAspect implements InitializingBean {

    private final Logger logger = LoggerFactory.getLogger(ParamIdempotentAspect.class);

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Autowired
    private DistributedLockService distributedLockService;

    static {
        OBJECT_MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
        OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
        OBJECT_MAPPER.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
        OBJECT_MAPPER.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    }

    @Around("@annotation(paramIdempotent)")
    public Object aroundCut(ProceedingJoinPoint joinPoint, ParamIdempotent paramIdempotent) throws Throwable {
        // 参数数组
        Object[] args = joinPoint.getArgs();

        if (args == null || args.length <= 0) {
            // 目标方法没有参数
            throw new IllegalArgumentException("目标方法无参数，不能使用ParamIdempotent注解");
        }

        // 排除属性后的json
        String argsJson;
        // 要排除的属性
        String[] excludeAttributes = paramIdempotent.excludeAttributes();
        if (excludeAttributes != null && excludeAttributes.length > 0) {
            ObjectMapper mapper = OBJECT_MAPPER.copy();
            SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept(Sets.newHashSet(excludeAttributes));
            SimpleFilterProvider provider = new SimpleFilterProvider();
            provider.addFilter("fieldFilter", filter);
            for (Object arg : args) {
                mapper.addMixIn(arg.getClass(), FieldFilterMixIn.class).setFilterProvider(provider);
            }
            argsJson = mapper.writeValueAsString(args);
        } else {
            argsJson = OBJECT_MAPPER.writeValueAsString(args);
        }

        // 生成MD5
        String md5 = DigestUtils.md5DigestAsHex(argsJson.getBytes(StandardCharsets.UTF_8));
        if (logger.isTraceEnabled()) {
            logger.trace("PARAM幂等性切面拦截，过滤后参数：{}", argsJson);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("PARAM幂等性切面拦截，参数MD5：{}", md5);
        }

        // 分布式锁加锁
        String lockKey = paramIdempotent.prefix() + md5;
        String uniqueValue = UUID.fastUUID().toString();
        boolean suc = distributedLockService.lock(lockKey, uniqueValue, paramIdempotent.interval());
        if (!suc) {
            // 加锁失败，重复请求
            throw new IllegalStateException("幂等性验证不通过，重复请求，请稍后刷新查看");
        }
        // 加锁成功
        try {
            // 执行目标方法
            return joinPoint.proceed();
        } finally {
            // 方法执行完毕，解锁
            distributedLockService.unlock(lockKey, uniqueValue);
        }

    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(distributedLockService, "distributedLockService不能为空");
    }


    @JsonFilter("fieldFilter")
    interface FieldFilterMixIn {
    }

}
